#pragma once

#include <memory>

#include "Log.hpp"
#include "InetAddr.hpp"
#include "Socket.hpp"

using namespace socket_ns;

// select服务器要正确的编写，需要借助一个第三方数组来完成，保存合法的，所有的fd到数组中，方便后期批量化统一添加
class SelectServer
{
	const static int N = sizeof(fd_set) * 8;
	const static int defaultfd = -1;

public:
	SelectServer(uint16_t port)
		: _port(port),
		  _listensock(std::make_unique<TcpSocket>())
	{
		InetAddr addr("0", 8888);
		_listensock->BuildListenSocket(addr);

		// 初始辅助数组
		for (int i = 0; i < N; i++)
		{
			_fd_arr[i] = defaultfd;
		}
		_fd_arr[0] = _listensock->SockFd();
	}

	void AcceptClient()
	{
		// 我们今天只关心了读，而读有：listensock 和 nornam sockfd
		InetAddr clientaddr;
		int sockfd = _listensock->Accepter(&clientaddr); // 这里调用accept会不会阻塞呢？？不会。因为事件已经就绪了
		if (sockfd < 0)
			return;

		LOG(DEBUG, "Get new Link, sockfd: %d, client info %s:%d\n", sockfd, clientaddr.ip().c_str(), clientaddr.port());
		// read/recv(sockfd); send(sockfd)?? 不能. 必须将新的fd，托管给select。如何托管呢？？？
		// 只要将新的fd添加到辅助数组中即可。

		int pos = 1;
		for (; pos < N; pos++) // 找到有空的位置
		{
			if (_fd_arr[pos] == defaultfd)
				break;
		}
		if (pos == N) // 这是访问满了的情况
		{
			::close(sockfd); // sockfd->Close();
			LOG(WARNING, "server is full!\n");
			return;
		}
		else // 没满就设置进辅助数组
		{
			_fd_arr[pos] = sockfd;
			LOG(DEBUG, "%d add to select array!\n", sockfd);
		}

		LOG(DEBUG, "curr fd_arr[] fd list : %s\n", RfdsToString().c_str());
	}

	void ServiceIO(int pos)
	{
		//已经是就绪的fd了，接收到就是有，没就收到就是断开了，要进行fd关闭
		char buff[1024];
		ssize_t n = ::recv(_fd_arr[pos], buff, sizeof(buff) - 1, 0);
		if(n > 0)	
		{
			buff[n] = 0; 
            std::cout << "client say# " << buff << std::endl;

            std::string echo_string = "[server echo]# ";
            echo_string += buff;
            ::send(_fd_arr[pos], echo_string.c_str(), echo_string.size(), 0);
		}
		else if(n == 0)		//
		{
			LOG(DEBUG, "%d is closed\n", _fd_arr[pos]);
            ::close(_fd_arr[pos]);
            _fd_arr[pos] = defaultfd;
            LOG(DEBUG, "curr fd_arr[] fd list : %s\n", RfdsToString().c_str());
		}
		else
        {
            LOG(DEBUG, "%d recv error\n", _fd_arr[pos]);
            ::close(_fd_arr[pos]);
            _fd_arr[pos] = defaultfd;
            LOG(DEBUG, "curr fd_arr[] fd list : %s\n", RfdsToString().c_str());
        }
	}

	void HandlerEvent(fd_set &rfds)
	{
		// 因为loop中每次都会对rfds进行重置，所以要进行处理
		// 如果是listensockfd，要继续保存在rfds中，等待就绪
		// 如果是已经就绪的fd，则进行处理
		for (int i = 0; i < N; i++)
		{
			if (_fd_arr[i] == defaultfd) // 不合法的
				continue;
			if (FD_ISSET(_fd_arr[i], &rfds)) // 在rfds中的有可能是listensockfd或者是就绪的fd
			{
				if (_fd_arr[i] == _listensock->SockFd()) // listensockfd
				{
					AcceptClient();
				}
				else
				{
					// 这里不就是普通的sockfd读事件就绪了吗？
					ServiceIO(i);
				}
			}
		}
	}

	void loop()
	{

		while (true)
		{
			// listensocket 等待新连接到来，等价于对方给我发送数据！我们作为读事件同一处理
			// 新连接到来 等价于 读事件就绪！
			// 首先要将listensock添加到select中！
			fd_set rfds;
			FD_ZERO(&rfds);
			int max_fd = defaultfd;

			for (int i = 0; i < N; i++)
			{
				if (_fd_arr[i] == defaultfd)
					continue;
				FD_SET(_fd_arr[i], &rfds); // 将合法的fd保存到rfds中
				if (max_fd < _fd_arr[i])
					max_fd = _fd_arr[i]; // 更新出最大的fd的值
			}

			struct timeval timeout = {1, 0};

			// select 同时等待的fd，是有上限的。因为fd_set是具体的数据类型，有自己的大小！
			// rfds是一个输入输出型参数，每次调用，都要对rfds进行重新设定!

			int n = select(max_fd + 1, &rfds, nullptr, nullptr, /*&timeout*/ nullptr);
			switch (n)
			{
			case 0:
				LOG(INFO, "timeout, %d.%d\n", timeout.tv_sec, timeout.tv_usec);
				break;
			case -1:
				LOG(ERROR, "select error...\n");
				break;
			default:
				LOG(DEBUG, "Event Happen. n : %d\n", n); // 底层有一个事件就绪，select为什么会一直通知我？因为：我们没有处理！
				HandlerEvent(rfds);						 // 处理
				break;
			}
		}
	}

	std::string RfdsToString()
	{
		std::string fd_str;
		for (int i = 0; i < N; i++)
		{
			if (_fd_arr[i] == defaultfd)
				continue;
			fd_str += std::to_string(_fd_arr[i]);
			fd_str += " ";
		}

		return fd_str;
	}

private:
	uint16_t _port;
	std::unique_ptr<Socket> _listensock;
	int _fd_arr[N]; // 辅助数组
};
